๐๏ธGitะฏัะฐ๐๏ธ
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonMain / kotlin / org / meshtastic / feature / firmware / UsbMaintenance.kt
Displaying Raw โข Download
feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/UsbMaintenance.kt 4d4070c8e1ed73a56ee2f054300a0f62e3fe2e62 (4d4070c8) Text, 11.66 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.firmware
Tff7b72import T7ee787org.meshtastic.core.common.util.CommonUri
Tff7b72import T7ee787org.meshtastic.core.model.DeviceHardware
Tff7b72import T7ee787org.meshtastic.core.model.SoftDeviceVariant
Tff7b72import T7ee787org.meshtastic.core.resources.Res
Tff7b72import T7ee787org.meshtastic.core.resources.UiText
Tff7b72import T7ee787org.meshtastic.core.resources.firmware_maintenance_no_release
Tff7b72import T7ee787org.meshtastic.core.resources.firmware_maintenance_not_a_bootloader_volume
Tff7b72import T7ee787org.meshtastic.core.resources.firmware_maintenance_softdevice_conflict
Tff7b72import T7ee787org.meshtastic.core.resources.firmware_maintenance_unknown_board
Tff7b72import T7ee787org.meshtastic.core.resources.firmware_maintenance_unknown_softdevice
Tff7b72import T7ee787org.meshtastic.core.resources.firmware_maintenance_unsupported_device
Tff7b72import T7ee787org.meshtastic.core.resources.firmware_maintenance_wrong_destination
T8b949e/**
* Which leg of a multi-pass USB/UF2 sequence a file-save prompt belongs to.
*
* Also the recomposition key for the instruction dialog in `AwaitingFileSaveState`. Every sequence shipped today is
* (FactoryErase | BootloaderUpgrade) โ Firmware, so the key always changes between passes; a future sequence with two
* consecutive identical steps would need a pass index added here.
*/
Tff7b72enum Tff7b72class T56d364UsbFileSaveStep Tb4b4b4{
T8b949e/**
* The release firmware image โ the only pass in a plain update, and the last pass of every maintenance sequence.
*/
Te6edf3FirmwareTb4b4b4,
T8b949e/** The factory-erase image, which wipes the internal filesystem and re-enters UF2 DFU. */
Te6edf3FactoryEraseTb4b4b4,
T8b949e/** An OTAFIX bootloader self-update image. */
Te6edf3BootloaderUpgradeTb4b4b4,
Tb4b4b4;
T8b949e/**
* True when writing this image destroys the device's application, making the *next* pass mandatory rather than
* optional. Drives back-navigation gating and the "no abort edge" retry behaviour.
*/
Tff7b72val Te6edf3isDestructiveTb4b4b4: Tffa657Boolean
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Tff7b72this Tff7b72!Tff7b72= Te6edf3Firmware
Tb4b4b4}
T8b949e/** A maintenance operation the user can start from the Ready state. */
Tff7b72enum Tff7b72class T56d364UsbMaintenanceRequest Tb4b4b4{
Te6edf3FactoryEraseTb4b4b4,
Te6edf3BootloaderUpgradeTb4b4b4,
Tb4b4b4}
T8b949e/** Why a maintenance action cannot run. Surfaced as explanatory copy rather than silently hiding the action. */
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUndocumentedPublicPropertyTa5d6ff"Tb4b4b4)
Tff7b72enum Tff7b72class T56d364UsbMaintenanceRefusal Tb4b4b4{
T8b949e/**
* The device's SoftDevice variant could not be resolved, so no erase image can be chosen safely. Covers an absent
* or malformed metadata asset, an unmapped hardware model, and a device reporting a target we have no mapping for.
*/
Te6edf3UnknownSoftDeviceTb4b4b4,
T8b949e/** The architecture has no UF2 erase path at all (ESP32, portduino). */
Te6edf3UnsupportedArchitectureTb4b4b4,
T8b949e/** No release firmware is selected, so there would be nothing to re-flash after erasing. */
Te6edf3NoFirmwareReleaseTb4b4b4,
T8b949e/** The picked destination is not on removable storage, so it cannot be a mounted bootloader volume. */
Te6edf3DestinationNotRemovableTb4b4b4,
T8b949e/**
* The picked volume exposes no readable `INFO_UF2.TXT`, so it is not an Adafruit-family UF2 bootloader drive. Also
* the outcome when a device rebooted into CDC-only bootloader mode, where no mass-storage volume exists at all.
*/
Te6edf3NotABootloaderVolumeTb4b4b4,
T8b949e/**
* The volume's reported SoftDevice contradicts the bundled map. One of the two is wrong and we cannot tell which,
* so refuse โ and treat the map row as suspect.
*/
Te6edf3SoftDeviceConflictTb4b4b4,
T8b949e/** No OTAFIX image is published for the Board-ID this volume reports. */
Te6edf3UnknownBoardIdTb4b4b4,
Tb4b4b4}
T8b949e/**
* The single mapping from [UsbMaintenanceRefusal] to user-facing copy.
*
* Shared by the ViewModel (wraps it in [org.meshtastic.feature.firmware.FirmwareUpdateState.Error] for a refusal raised
* mid-flow) and the pre-flight card (resolves it via [UiText.asString] next to the disabled button) so the two call
* sites can never drift out of sync with each other.
*/
Tff7b72internal Tff7b72fun Td2a8ffusbMaintenanceRefusalMessageTb4b4b4(Te6edf3reasonTb4b4b4: Te6edf3UsbMaintenanceRefusalTb4b4b4)Tb4b4b4: Te6edf3UiText Tff7b72= Tff7b72when Tb4b4b4(Te6edf3reasonTb4b4b4) Tb4b4b4{
Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnknownSoftDevice Tff7b72-Tff7b72> Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3firmware_maintenance_unknown_softdeviceTb4b4b4)
Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnsupportedArchitecture Tff7b72-Tff7b72>
Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3firmware_maintenance_unsupported_deviceTb4b4b4)
Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3NoFirmwareRelease Tff7b72-Tff7b72> Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3firmware_maintenance_no_releaseTb4b4b4)
Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3DestinationNotRemovable Tff7b72-Tff7b72>
Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3firmware_maintenance_wrong_destinationTb4b4b4)
Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3NotABootloaderVolume Tff7b72-Tff7b72>
Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3firmware_maintenance_not_a_bootloader_volumeTb4b4b4)
Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3SoftDeviceConflict Tff7b72-Tff7b72> Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3firmware_maintenance_softdevice_conflictTb4b4b4)
Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnknownBoardId Tff7b72-Tff7b72> Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3firmware_maintenance_unknown_boardTb4b4b4)
Tb4b4b4}
T8b949e/**
* Which maintenance affordances the Ready state should show, precomputed by the ViewModel.
*
* Follows the `showBootloaderWarning` precedent: the screen stays dumb and the decision stays unit-testable.
*
* @property show Whether the maintenance section appears at all.
* @property eraseRefusal Non-null when erase is shown but cannot run; the reason is displayed and the action disabled.
* @property showBootloaderUpgrade Whether a bootloader-upgrade action is offered. Absent (not refused) when no image is
* mapped for the board โ an unmapped board is a coverage gap, not a safety decision the user can act on.
*/
Tff7b72data Tff7b72class T56d364UsbMaintenanceGateTb4b4b4(
Tff7b72val Te6edf3showTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4,
Tff7b72val Te6edf3eraseRefusalTb4b4b4: Te6edf3UsbMaintenanceRefusal? Tff7b72= Tff7b72nullTb4b4b4,
Tff7b72val Te6edf3showBootloaderUpgradeTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)
T8b949e/**
* Decides which maintenance actions are available for [hardware] on [updateMethod].
*
* Pure and total: every refusal is represented, and nothing here can fall back to a default erase image.
*
* @param hasRelease Whether a firmware release is selected. Erasing without one would strand the device with no
* application, so the whole section is hidden.
*/
Tff7b72internal Tff7b72fun Td2a8ffusbMaintenanceGateTb4b4b4(
Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4,
Te6edf3updateMethodTb4b4b4: Te6edf3FirmwareUpdateMethodTb4b4b4,
Te6edf3hasReleaseTb4b4b4: Tffa657BooleanTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3UsbMaintenanceGate Tb4b4b4{
Tff7b72val Te6edf3uf2Architecture Tff7b72= Te6edf3hardwareTb4b4b4.Te6edf3isNrf52Arc Tff7b72|Tff7b72| Te6edf3hardwareTb4b4b4.Te6edf3isRp2040Arc
Tff7b72if Tb4b4b4(Te6edf3updateMethod Tff7b72!is Te6edf3FirmwareUpdateMethodTb4b4b4.Te6edf3Usb Tff7b72|Tff7b72| Tff7b72!Te6edf3uf2Architecture Tff7b72|Tff7b72| Tff7b72!Te6edf3hasReleaseTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3UsbMaintenanceGateTb4b4b4(Te6edf3show Tff7b72= Tff7b72falseTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3eraseRefusal Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3eraseUf2ForTb4b4b4(Te6edf3hardwareTb4b4b4) Tff7b72!Tff7b72= Tff7b72null Tff7b72-Tff7b72> Tff7b72null
Te6edf3hardwareTb4b4b4.Te6edf3isNrf52Arc Tff7b72-Tff7b72> Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnknownSoftDevice
Tff7b72else Tff7b72-Tff7b72> Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnsupportedArchitecture
Tb4b4b4}
Tff7b72return Te6edf3UsbMaintenanceGateTb4b4b4(
Te6edf3show Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3eraseRefusal Tff7b72= Te6edf3eraseRefusalTb4b4b4,
T8b949e// nRF-only: RP2040 boards run no Adafruit bootloader, so OTAFIX does not apply. This is a visibility hint only
T8b949e// โ which image gets written is decided later from the Board-ID the drive reports.
Te6edf3showBootloaderUpgrade Tff7b72= Te6edf3hardwareTb4b4b4.Te6edf3isNrf52Arc Tff7b72&Tff7b72& Te6edf3otafixSupportsTargetTb4b4b4(Te6edf3hardwareTb4b4b4.Te6edf3effectiveTargetTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e/**
* What a mounted UF2 bootloader volume says about itself, read from its `INFO_UF2.TXT`.
*
* @property boardId The `Board-ID:` line โ unique per board, and stable across bootloader vintages.
* @property softDevice The installed SoftDevice, when the bootloader reports one. `null` on RP2040 (no SoftDevice
* exists) and on bootloaders predating the `uf2_init` that appends the line.
*/
Tff7b72internal Tff7b72data Tff7b72class T56d364MaintenanceVolumeTb4b4b4(Tff7b72val Te6edf3boardIdTb4b4b4: Tffa657StringTb4b4b4, Tff7b72val Te6edf3softDeviceTb4b4b4: Te6edf3SoftDeviceVariant?Tb4b4b4)
T8b949e/** Outcome of vetting a user-picked volume before anything is written to it. */
Tff7b72internal Tff7b72sealed Tff7b72interface T56d364VolumeInspection Tb4b4b4{
Tff7b72data Tff7b72class T56d364AcceptedTb4b4b4(Tff7b72val Te6edf3volumeTb4b4b4: Te6edf3MaintenanceVolumeTb4b4b4) Tb4b4b4: Te6edf3VolumeInspection
Tff7b72data Tff7b72class T56d364RejectedTb4b4b4(Tff7b72val Te6edf3reasonTb4b4b4: Te6edf3UsbMaintenanceRefusalTb4b4b4) Tb4b4b4: Te6edf3VolumeInspection
Tb4b4b4}
T8b949e/**
* Vets [treeUri] as a UF2 bootloader volume before it is written to.
*
* Two independent checks, cheapest first: the volume must be removable, and it must expose a readable `INFO_UF2.TXT`
* with a `Board-ID:` line. The second is the load-bearing one โ it is positive proof of an Adafruit-family bootloader
* drive, where a removability heuristic only makes "somewhere on internal storage" less likely. This is what stops the
* common mis-tap (saving into Downloads) from being indistinguishable from a successful flash.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4) T8b949e// two independent vetting gates, each of which must reject immediately
Tff7b72internal Tff7b72suspend Tff7b72fun Td2a8ffinspectMaintenanceVolumeTb4b4b4(Te6edf3treeUriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3fileHandlerTb4b4b4: Te6edf3FirmwareFileHandlerTb4b4b4)Tb4b4b4: Te6edf3VolumeInspection Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3fileHandlerTb4b4b4.Te6edf3isRemovableDestinationTb4b4b4(Te6edf3treeUriTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3VolumeInspectionTb4b4b4.Te6edf3RejectedTb4b4b4(Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3DestinationNotRemovableTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3info Tff7b72=
Te6edf3fileHandlerTb4b4b4.Te6edf3readSiblingTextTb4b4b4(Te6edf3treeUriTb4b4b4, Te6edf3INFO_UF2_FILE_NAMETb4b4b4)
Tff7b72?: Tff7b72return Te6edf3VolumeInspectionTb4b4b4.Te6edf3RejectedTb4b4b4(Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3NotABootloaderVolumeTb4b4b4)
Tff7b72val Te6edf3boardId Tff7b72= Te6edf3parseUf2BoardIdTb4b4b4(Te6edf3infoTb4b4b4) Tff7b72?: Tff7b72return Te6edf3VolumeInspectionTb4b4b4.Te6edf3RejectedTb4b4b4(Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3NotABootloaderVolumeTb4b4b4)
Tff7b72return Te6edf3VolumeInspectionTb4b4b4.Te6edf3AcceptedTb4b4b4(Te6edf3MaintenanceVolumeTb4b4b4(Te6edf3boardId Tff7b72= Te6edf3boardIdTb4b4b4, Te6edf3softDevice Tff7b72= Te6edf3parseUf2SoftDeviceTb4b4b4(Te6edf3infoTb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/** Which image to write, or why not. */
Tff7b72internal Tff7b72sealed Tff7b72interface T56d364MaintenanceImageChoice Tb4b4b4{
Tff7b72data Tff7b72class T56d364ResolvedTb4b4b4(Tff7b72val Te6edf3assetTb4b4b4: Te6edf3MaintenanceUf2Tb4b4b4) Tb4b4b4: Te6edf3MaintenanceImageChoice
Tff7b72data Tff7b72class T56d364RefusedTb4b4b4(Tff7b72val Te6edf3reasonTb4b4b4: Te6edf3UsbMaintenanceRefusalTb4b4b4) Tb4b4b4: Te6edf3MaintenanceImageChoice
Tb4b4b4}
T8b949e/**
* Chooses the image for [request], preferring what the mounted [volume] reports over what the bundled map predicted.
*
* Total over both requests and every refusal, with no default image on any path. Called only after
* [inspectMaintenanceVolume] accepts, and always before anything is written โ so a refusal here costs the user a
* message, never a half-flashed device.
*/
Tff7b72internal Tff7b72fun Td2a8ffchooseMaintenanceImageTb4b4b4(
Te6edf3requestTb4b4b4: Te6edf3UsbMaintenanceRequestTb4b4b4,
Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4,
Te6edf3volumeTb4b4b4: Te6edf3MaintenanceVolumeTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3MaintenanceImageChoice Tff7b72= Tff7b72when Tb4b4b4(Te6edf3requestTb4b4b4) Tb4b4b4{
Te6edf3UsbMaintenanceRequestTb4b4b4.Te6edf3FactoryErase Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3hardwareTb4b4b4.Te6edf3isNrf52ArcTb4b4b4) Tb4b4b4{
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3resolution Tff7b72= Te6edf3resolveNrfEraseImageTb4b4b4(Te6edf3hardwareTb4b4b4.Te6edf3softDeviceVariantTb4b4b4, Te6edf3volumeTb4b4b4.Te6edf3softDeviceTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3EraseImageResolutionTb4b4b4.Te6edf3Resolved Tff7b72-Tff7b72> Te6edf3MaintenanceImageChoiceTb4b4b4.Te6edf3ResolvedTb4b4b4(Te6edf3resolutionTb4b4b4.Te6edf3assetTb4b4b4)
Tff7b72is Te6edf3EraseImageResolutionTb4b4b4.Te6edf3Conflict Tff7b72-Tff7b72>
Te6edf3MaintenanceImageChoiceTb4b4b4.Te6edf3RefusedTb4b4b4(Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3SoftDeviceConflictTb4b4b4)
Te6edf3EraseImageResolutionTb4b4b4.Te6edf3Unresolved Tff7b72-Tff7b72>
Te6edf3MaintenanceImageChoiceTb4b4b4.Te6edf3RefusedTb4b4b4(Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnknownSoftDeviceTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// RP2040: pico_erase is board-agnostic and there is no SoftDevice to reconcile.
Te6edf3eraseUf2ForTb4b4b4(Te6edf3hardwareTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3MaintenanceImageChoiceTb4b4b4.Te6edf3ResolvedTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tff7b72?: Te6edf3MaintenanceImageChoiceTb4b4b4.Te6edf3RefusedTb4b4b4(Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnsupportedArchitectureTb4b4b4)
Tb4b4b4}
Te6edf3UsbMaintenanceRequestTb4b4b4.Te6edf3BootloaderUpgrade Tff7b72-Tff7b72>
Te6edf3otafixUf2ForBoardIdTb4b4b4(Te6edf3volumeTb4b4b4.Te6edf3boardIdTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3MaintenanceImageChoiceTb4b4b4.Te6edf3ResolvedTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tff7b72?: Te6edf3MaintenanceImageChoiceTb4b4b4.Te6edf3RefusedTb4b4b4(Te6edf3UsbMaintenanceRefusalTb4b4b4.Te6edf3UnknownBoardIdTb4b4b4)
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.08s